/**
 * @description 地图编辑器
 * @author mirahs
 * @email 2892727493@qq.com
 */
import App from "../../framework/scripts/App"
import AppUtil from "../../framework/scripts/AppUtil"
import PanelMgr from "../../framework/scripts/view/PanelMgr"
import MapData from "../map/MapData"
import Core from "../core/Core"
import HtmlUtil from "../util/HtmlUtil"
import MapUtil from "../util/MapUtil"
import PanelChoiceDraw from "../view/PanelChoiceDraw"
import MouseAction from "../map/MapAction"
import MapInfo from "../map/MapInfo"
import PanelMapNew from "../view/PanelMapNew"
import PanelMapOpen from "../view/PanelMapOpen"
import GameConstants from "../GameConstants"

const { ccclass } = cc._decorator


class DrawData {
    public constructor(public prefab: cc.Node, public parent: cc.Node) { }
}


@ccclass
export default class Editor extends cc.Component {
    private nodeMap: cc.Node
    private graphicsGrid: cc.Graphics
    private graphicsPolygon: cc.Graphics

    private lblMap: cc.Label
    private lblScale: cc.Label
    private lblPos: cc.Label
    private lblGrid: cc.Label

    private _drawMap: Map<number, DrawData> = new Map()
    private _gridMap: Map<string, cc.Node> = new Map()


    protected onLoad(): void {
        App.init()
        Core.init()

        this.init()
    }

    protected start(): void {
        this.loadMap('load')
    }


    //#region 初始化
    private init() {
        this.initButton()
        this.initMap()
        this.initStatus()
    }


    private initButton() {
        const topNode = this.node.getChildByName('top')

        const btnNew = topNode.getChildByName('btnNew')
        btnNew.on('click', () => {
            PanelMgr.show(PanelMapNew, (fileName: string, texture2D: cc.Texture2D, gridWidth: number, gridHeight: number) => {
                MapInfo.mapFileName = fileName
                MapInfo.dataFileName = this.mapFileToDataFile(MapInfo.mapFileName)
                MapInfo.gridWidth = gridWidth
                MapInfo.gridHeight = gridHeight

                this.nodeMap.getComponent(cc.Sprite).spriteFrame = new cc.SpriteFrame(texture2D)
                this.loadMap('new')
            })
        })

        const btnOpen = topNode.getChildByName('btnOpen')
        btnOpen.on('click', () => {
            PanelMgr.show(PanelMapOpen, (fileName: string, texture2D: cc.Texture2D, dataFileName: string, mapData: MapData) => {
                MapInfo.mapFileName = fileName
                MapInfo.dataFileName = dataFileName
                MapInfo.gridWidth = mapData.grid_width
                MapInfo.gridHeight = mapData.grid_height

                this.nodeMap.getComponent(cc.Sprite).spriteFrame = new cc.SpriteFrame(texture2D)
                this.loadMap('open', mapData)
            })
        })

        const btnSave = topNode.getChildByName('btnSave')
        btnSave.on('click', () => HtmlUtil.saveFile(MapInfo.dataFileName, this.mapGetData()))

        const radioAction = topNode.getChildByName('radioAction').getComponent(cc.ToggleContainer)
        radioAction.node.getChildByName(MapInfo.mouseAction).getComponent(cc.Toggle).isChecked = true
        radioAction.checkEvents.push(this.newEventHandler(this.node, 'Editor', 'onRadioAction'))

        const btnDrawType = topNode.getChildByName('btnDrawType')
        btnDrawType.children[0].getComponent(cc.Label).string = GameConstants.mapDrawTypes[MapInfo.drawType - 1][0]
        btnDrawType.on(cc.Node.EventType.TOUCH_START, () => {
            PanelMgr.show(PanelChoiceDraw, GameConstants.mapDrawTypes, (drawIdx: number) => {
                const drawType = drawIdx + 1
                if (drawType == MapInfo.drawType) return

                MapInfo.drawType = drawType
                btnDrawType.children[0].getComponent(cc.Label).string = GameConstants.mapDrawTypes[drawIdx][0]
            })
        })

        const checkboxGrid = topNode.getChildByName('checkboxGrid').getComponent(cc.Toggle)
        checkboxGrid.isChecked = MapInfo.isShowGrid
        checkboxGrid.node.on('click', (toggle: cc.Toggle) => this.graphicsGrid.node.active = MapInfo.isShowGrid = !toggle.isChecked)

        const checkboxPolygon = topNode.getChildByName('checkboxPolygon').getComponent(cc.Toggle)
        checkboxPolygon.isChecked = MapInfo.isShowPolygon
        checkboxPolygon.node.on('click', (toggle: cc.Toggle) => MapInfo.isShowPolygon = !toggle.isChecked)
    }

    private initMap() {
        this.nodeMap = this.node.getChildByName('middle').getChildByName('map')

        this.nodeMap.on(cc.Node.EventType.TOUCH_START, this.onMapTouchStart, this)
        this.nodeMap.on(cc.Node.EventType.TOUCH_MOVE, this.onMapTouchMove, this)

        this.nodeMap.on(cc.Node.EventType.MOUSE_WHEEL, this.onMapMouseWheel, this)
        this.nodeMap.on(cc.Node.EventType.MOUSE_DOWN, this.onMapMouseDown, this)
    }

    private initStatus() {
        const bottomNode = this.node.getChildByName('bottom')

        this.lblMap = bottomNode.getChildByName('lblMap').getComponent(cc.Label)
        this.lblScale = bottomNode.getChildByName('lblScale').getComponent(cc.Label)
        this.lblPos = bottomNode.getChildByName('lblPos').getComponent(cc.Label)
        this.lblGrid = bottomNode.getChildByName('lblGrid').getComponent(cc.Label)
    }


    private initDrawData() {
        this._drawMap.clear()
        this._gridMap.clear()

        for (let i = 0; i < GameConstants.mapDrawTypes.length; i++) {
            const drawType = i + 1

            const val = GameConstants.mapDrawTypes[i]
            const name = drawType.toString()
            const color = val[1]

            const prefab = AppUtil.newSpriteNode(name)
            prefab.setContentSize(MapInfo.gridWidth - 2, MapInfo.gridHeight - 2)
            prefab.color = new cc.Color().fromHEX(color)
            prefab.opacity = Number(val[2] ?? '255')

            const parent = new cc.Node()
            parent.parent = this.nodeMap
            parent.setAnchorPoint(cc.Vec2.ZERO)

            this._drawMap.set(drawType, new DrawData(prefab, parent))
        }
    }

    private initMapGridGraphics() {
        const node = new cc.Node()
        node.parent = this.nodeMap
        node.setAnchorPoint(cc.Vec2.ZERO)

        this.graphicsGrid = node.addComponent(cc.Graphics)
        this.graphicsGrid.strokeColor = cc.Color.RED

        for (let i = 1; i < MapInfo.rows; i++) {
            this.mapGridDrawCol(i)
        }
        for (let i = 1; i < MapInfo.cols; i++) {
            this.mapGridDrawRow(i)
        }
    }

    private initMapPloygonGraphics() {
        const node = new cc.Node()
        node.parent = this.nodeMap
        node.setAnchorPoint(cc.Vec2.ZERO)

        this.graphicsPolygon = node.addComponent(cc.Graphics)
        this.graphicsPolygon.strokeColor = cc.Color.GREEN
    }
    //#endregion


    //#region 加载地图
    private loadMap(type: string, mapData?: MapData) {
        this.nodeMap.destroyAllChildren()

        this.initDrawData()
        this.initMapPloygonGraphics()

        if (type != 'load') {
            this.nodeMap.scale = 1
            this.nodeMap.setPosition(cc.Vec2.ZERO)
        }

        MapInfo.width = this.nodeMap.width
        MapInfo.height = this.nodeMap.height
        MapInfo.rows = Math.ceil(MapInfo.width / MapInfo.gridWidth)
        MapInfo.cols = Math.ceil(MapInfo.height / MapInfo.gridHeight)

        this.initMapGridGraphics()

        if (mapData) {
            this.loadMapData(mapData)
        }

        this.statusShow()
    }

    private loadMapData(mapData: MapData): void {
        for (let y = 0; y < mapData.cols; y++) {
            for (let x = 0; x < mapData.rows; x++) {
                const drawType = mapData.data[y][x]
                const drawData = this._drawMap.get(drawType)
                if (!drawData) continue

                const grid = cc.instantiate(drawData.prefab)
                grid.name = `${drawType}`
                grid.parent = drawData.parent
                grid.setPosition(MapUtil.gridToPos(x, y))

                const gridIdx = MapUtil.xyToGridText(x, y)
                this._gridMap.set(gridIdx, grid)
            }
        }
    }
    //#endregion


    //#region 地图触摸和鼠标事件
    private onMapTouchStart(event: cc.Event.EventTouch) {
        this.mapTouch(event, true)
    }

    private onMapTouchMove(event: cc.Event.EventTouch) {
        this.mapTouch(event)
    }

    private onMapMouseWheel(event: cc.Event.EventMouse) {
        const oldScale = this.nodeMap.scale
        const oldPos = this.nodeMap.getPosition()

        const mousePos = this.nodeMap.parent.convertToNodeSpaceAR(event.getLocation())
        const offset = this.nodeMap.getPosition().sub(mousePos)

        let scale = oldScale
        scale += event.getScrollY() > 0 ? 0.1 : -0.1

        offset.mulSelf(scale / oldScale)
        const pos = mousePos.add(offset)

        this.nodeMap.scale = scale
        this.nodeMap.setPosition(pos)

        if (!this.mapCheckBoundary(pos.x, pos.y)) {
            this.nodeMap.scale = oldScale
            this.nodeMap.setPosition(oldPos)
        }

        this.statusShowScale()
    }

    private _mouseLastDownTime: number = 0
    private _polygonPoints: cc.Vec2[] = []
    private onMapMouseDown(event: cc.Event.EventMouse) {
        if (!MapInfo.isShowPolygon || MapInfo.mouseAction == MouseAction.Move) return

        if (this._polygonPoints.length == 0) {
            this.mapPolygonClearGraphics()
        }

        const pos = this.nodeMap.convertToNodeSpaceAR(event.getLocation())
        const time = (new Date()).getTime()

        if (time - this._mouseLastDownTime < 300) {
            this.mapPolygonWork()
            this.mapPolygonClear()
        } else {
            this._mouseLastDownTime = time
            this._polygonPoints.push(pos)

            this.mapPolygonDrawCircle(pos)
        }
    }
    //#endregion


    //#region 地图相关
    private mapTouch(event: cc.Event.EventTouch, isStart?: boolean) {
        const pos = this.nodeMap.convertToNodeSpaceAR(event.getLocation())
        this.statusShowPosGrid(pos)

        if (MapInfo.isShowPolygon && MapInfo.mouseAction != MouseAction.Move) return

        if (isStart) {
            this.mapPolygonClearGraphics()
        }

        if (MapInfo.mouseAction == MouseAction.Move) {
            const delta = event.getDelta()
            this.mapActionMove(delta)
        } else if (MapInfo.mouseAction == MouseAction.Draw) {
            this.mapActionDraw(pos)
        } else if (MapInfo.mouseAction == MouseAction.Clear) {
            this.mapActionClear(pos)
        }
    }

    private mapActionMove({ x, y }: cc.Vec2) {
        const xNew = this.nodeMap.x + x
        const yNew = this.nodeMap.y + y

        this.nodeMap.setPosition(this.mapFixBoundary(xNew, yNew))
    }

    private mapActionDraw(pos: cc.Vec2) {
        const gridIdx = MapUtil.pixelToGridText(pos)
        const drawData = this._drawMap.get(MapInfo.drawType)

        let grid: cc.Node = this._gridMap.get(gridIdx)
        if (grid && grid.name == drawData.prefab.name) return
        if (grid) {
            grid.destroy()
        }

        grid = cc.instantiate(drawData.prefab)
        grid.parent = drawData.parent
        grid.setPosition(MapUtil.pixelToPos(pos))

        this._gridMap.set(gridIdx, grid)
    }

    private mapActionClear(pos: cc.Vec2) {
        const gridIdx = MapUtil.pixelToGridText(pos)

        const grid: cc.Node = this._gridMap.get(gridIdx)
        if (!grid) return

        grid.destroy()
        this._gridMap.delete(gridIdx)
    }

    private mapGridDrawRow(row: number) {
        this.graphicsGrid.moveTo(0, row * MapInfo.gridHeight)
        this.graphicsGrid.lineTo(this.nodeMap.width, row * MapInfo.gridHeight)

        this.graphicsGrid.stroke()
    }

    private mapGridDrawCol(col: number) {
        this.graphicsGrid.moveTo(col * MapInfo.gridWidth, 0)
        this.graphicsGrid.lineTo(col * MapInfo.gridWidth, this.nodeMap.height)

        this.graphicsGrid.stroke()
    }

    private mapPolygonDrawCircle(pos: cc.Vec2) {
        this.graphicsPolygon.lineWidth = 10
        this.graphicsPolygon.circle(pos.x, pos.y, 1)
        this.graphicsPolygon.stroke()

        if (this._polygonPoints.length >= 2) {
            const lastPos = this._polygonPoints[this._polygonPoints.length - 2]
            this.mapPolygonDrawLine(pos, lastPos)
        }
    }

    private mapPolygonDrawLine(pos: cc.Vec2, pos2: cc.Vec2) {
        this.graphicsPolygon.lineWidth = 5
        this.graphicsPolygon.moveTo(pos.x, pos.y)
        this.graphicsPolygon.lineTo(pos2.x, pos2.y)
        this.graphicsPolygon.stroke()
    }

    private mapPolygonWork(): void {
        if (this._polygonPoints.length < 3) return

        this.mapPolygonDrawLine(this._polygonPoints[0], this._polygonPoints[this._polygonPoints.length - 1])

        const firstPos = this._polygonPoints[0]
        let xmin = firstPos.x; let xmax = firstPos.x; let ymin = firstPos.y; let ymax = firstPos.y
        for (let pos of this._polygonPoints) {
            if (pos.x < xmin) xmin = pos.x
            if (pos.x > xmax) xmax = pos.x
            if (pos.y < ymin) ymin = pos.y
            if (pos.y > ymax) ymax = pos.y
        }

        const minGrid = MapUtil.pixelToGrid(cc.v2(xmin, ymin))
        const maxGrid = MapUtil.pixelToGrid(cc.v2(xmax, ymax))
        for (let xgrid = minGrid.x; xgrid <= maxGrid.x; xgrid++) {
            for (let ygrid = minGrid.y; ygrid <= maxGrid.y; ygrid++) {
                const gridPos = MapUtil.gridToPos(xgrid, ygrid)
                if (cc.Intersection.pointInPolygon(gridPos, this._polygonPoints)) {
                    if (MapInfo.mouseAction == MouseAction.Draw) {
                        this.mapActionDraw(gridPos)
                    } else if (MapInfo.mouseAction == MouseAction.Clear) {
                        this.mapActionClear(gridPos)
                    }
                }
            }
        }
    }

    private mapPolygonClear(): void {
        this._mouseLastDownTime = 0
        this._polygonPoints = []
    }

    private mapPolygonClearGraphics() {
        this.mapPolygonClear()
        this.graphicsPolygon.clear()
    }

    private mapGetData(): MapData {
        const mapData = new MapData()

        mapData.map_file = MapInfo.mapFileName
        mapData.width = this.nodeMap.width
        mapData.height = this.nodeMap.height
        mapData.grid_width = MapInfo.gridWidth
        mapData.grid_height = MapInfo.gridHeight
        mapData.rows = MapInfo.rows
        mapData.cols = MapInfo.cols

        const data: number[][] = []
        for (let y = 0; y < mapData.cols; y++) {
            data[y] = []
            for (let x = 0; x < mapData.rows; x++) {
                let flag = 0
                const gridIdx = MapUtil.xyToGridText(x, y)
                const grid = this._gridMap.get(gridIdx)
                if (grid) {
                    flag = parseInt(grid.name)
                }
                data[y][x] = flag
            }
        }
        mapData.data = data

        return mapData
    }

    private mapFixBoundary(x: number, y: number): cc.Vec2 {
        const scale = this.nodeMap.scale

        if (x < -this.nodeMap.width * scale + this.nodeMap.parent.width) x = -this.nodeMap.width * scale + this.nodeMap.parent.width
        if (x > 0) x = 0
        if (y < -this.nodeMap.height * scale + this.nodeMap.parent.height) y = -this.nodeMap.height * scale + this.nodeMap.parent.height
        if (y > 0) y = 0

        return cc.v2(x, y)
    }

    private mapCheckBoundary(x: number, y: number): boolean {
        const scale = this.nodeMap.scale

        return x >= -this.nodeMap.width * scale + this.nodeMap.parent.width && x <= 0 &&
            y >= -this.nodeMap.height * scale + this.nodeMap.parent.height && y <= 0
    }

    private mapFileToDataFile(mapFileName: string): string {
        return mapFileName.substring(0, mapFileName.lastIndexOf('.')) + '.json'
    }
    //#endregion


    //#region 状态栏
    private statusShow() {
        this.statusShowMap()
        this.statusShowScale()
        this.statusShowPosGrid()
    }

    private statusShowMap() {
        this.lblMap.string = `地图：${MapInfo.mapFileName}, ${MapInfo.width}x${MapInfo.height}, ${MapInfo.gridWidth}x${MapInfo.gridHeight}, ${MapInfo.rows}x${MapInfo.cols}`
    }

    private statusShowScale() {
        const scale = this.nodeMap.scale.toString().slice(0, 3)
        this.lblScale.string = `缩放: ${scale}`
    }

    private statusShowPosGrid(pos: cc.Vec2 = cc.Vec2.ZERO) {
        const grid = MapUtil.pixelToGrid(pos)
        this.lblPos.string = `坐标：${Math.floor(pos.x)}x${Math.floor(pos.y)}`
        this.lblGrid.string = `网格：${grid.x}x${grid.y}`
    }
    //#endregion


    //#region 其他
    private onRadioAction(toggle: cc.Toggle) {
        MapInfo.mouseAction = toggle.node.name as MouseAction
    }

    private newEventHandler(target: cc.Node, component: string, handler: string): cc.Component.EventHandler {
        const eventHandler = new cc.Component.EventHandler()
        eventHandler.target = target
        eventHandler.component = component
        eventHandler.handler = handler

        return eventHandler
    }
    //#endregion
}
